package com.demo.webapp.views.freemarker.directive.core;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.views.freemarker.FreemarkerManager;

import com.demo.common.ClassLoaderUtils;
import com.demo.common.StringUtils;
import com.demo.core.model.Site;
import com.demo.webapp.context.MultisiteContextHolder;
import com.demo.webapp.views.freemarker.ScopesHashModel;
import com.demo.webapp.views.freemarker.directive.core.validation.ParameterValidateable;
import com.demo.webapp.views.freemarker.directive.core.validation.exception.ParameterValidateException;
import com.demo.webapp.views.freemarker.directive.model.Attribute;
import com.demo.webapp.views.freemarker.directive.model.DirectiveModel;
import com.opensymphony.xwork2.ognl.OgnlValueStack;

import freemarker.core.Environment;
import freemarker.core.Environment.TemplateDirectiveBodyEx;
import freemarker.core.TemplateElement;
import freemarker.ext.beans.BeanModel;
import freemarker.ext.servlet.HttpRequestHashModel;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleHash;
import freemarker.template.Template;
import freemarker.template.TemplateDateModel;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;

/**
 * 自定义指令支持类 提供自定义指令的基础方法 为模板的模板提供支持 
 * <br>
 * <pre>
 * 1.为基础指令提供一般方法支持
 * </pre>
 * @author ryuu.kk
 * 
 */
public abstract class BaseDirectiveSupport implements TemplateDirectiveModel, ParameterValidateable {

	/**
	 * 自定义指令模型
	 */
	private DirectiveModel model;
	
	/**
	 * 指令名
	 */
	private String name;
	
	/**
	 * body所包含的内容是按照模板整体渲染还是按照自定义标签内部渲染
	 *  默认false由自定义指令渲染。
	 */
	protected boolean isRender = false;

	/**
	 * 指令名称
	 */
	protected String direcitveName;
	
	@SuppressWarnings("rawtypes")
	@Override
	public void execute(Environment env, Map params, TemplateModel[] loopVars,
			TemplateDirectiveBody body) throws TemplateException, IOException {

		boolean support = false;
		// 注释该段,所有自定义指令都被支持
		//if (body instanceof TemplateDirectiveBodyEx) {
		//	support = true;
		//}
		// 允许所有
		support = true;
		TemplateElement bodyElement = null;
		if (loopVars == null) {

		}

		// 支持自定义指令模板
		if (support) {
			// 定义模板字符串
			String source = "";
			
			Configuration config = (Configuration) env.getConfiguration().clone();
			//使用[@]语法
			// 得到整体模板
			TemplateHashModel model = env.getDataModel();
			// 得到整体模板的输入流
			Writer writer = env.getOut();
			// 创建模板数据源
			SimpleHash hash = createSimpleHash(ObjectWrapper.BEANS_WRAPPER, model);
			//config.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
			TemplateDirectiveBodyEx bodyEx = (TemplateDirectiveBodyEx) body;
			
			// 自定义指令内部模板的渲染
			if (body == null) {
				// 强制模板渲染
				isRender = false;
			} else {
				// 自定义指令内部存在模板体
				// 得到整体模板中的自定义指令所嵌套的模板(子模板)
				bodyElement = bodyEx.getTemplateElement();
				// 获得指令内部的模板
				source = bodyElement.getSource();
			}
			
			try {
				// 前置处理, 取得自定义指令逻辑
				preWriter(env, bodyEx, loopVars, hash, params);
				// 自定义表单内部的模板进行处理
				source = processSource(source, hash, params);
				// 设置模板名称(方便缓存)
				Iterator<?> keyIterator = params.keySet().iterator();
				List<String> keySorts = new ArrayList<String>();
				while (keyIterator.hasNext()) {
					keySorts.add(keyIterator.next().toString());
				}
				// 配需参数
				Collections.sort(keySorts);
				String keyToken = "";
				for (String key : keySorts) {
					keyToken += (key + "->" + params.get(key) + "&");
				}
				
				if (isRender) {
					if (body != null) {
						// 嵌套模板(子模板)以整体模板数据进行渲染
						body.render(writer);
					}
				} else {
					if (!StringUtils.isEmpty(source)) {
						// 嵌套模板在自定义指令的生命周期内执行渲染
						// (数据来源也是按照自定义指令中得到的数据进行,实现多条同一指令,相互数据不影响)
						Template t = new Template(direcitveName + ":" + keyToken, new StringReader(source), config);
						t.process(hash, writer);
						//writer.flush();
						//writer.close()
					}
				}
				// 后处理
				afterInvoke(writer);
				
			} catch (Exception e) {
				e.printStackTrace();
				// 错误消息
				source = "<div style='color:red;display:black;min-height:64px;'>";
				source += "<p> ********************************************** </p>";
				source += "<p> * 指令" + this.name + "出现错误. </p>";
				source += "<p> * 以下是错误消息：</p>";
				source += "<p> * ${error}</p>";
				source += "<p> ********************************************** </p></div>";
				hash.put("error", e.getMessage());
				Template t = new Template("errorTemplate", new StringReader(source), config);
				t.process(hash, writer);
			}
		}
	}

	/**
	 * 自定义标签的逻辑处理
	 * @param env Environment
	 * @param bodyEx TemplateDirectiveBodyEx
	 * @param loopVars TemplateModel[]
	 * @param hash SimpleHash
	 * @param params Map
	 * @throws IOException
	 */
	@SuppressWarnings("rawtypes")
	public abstract void preWriter(Environment env, TemplateDirectiveBodyEx bodyEx, TemplateModel[] loopVars, SimpleHash hash, Map params)
			throws IOException, ParameterValidateException, TemplateException;

	/**
	 * 获得自定义标签的模板路径
	 * @param params 参数Map
	 * @return String 模板路径
	 */
	@SuppressWarnings("rawtypes")
	public abstract String getTemplatePath(Map params);
	
	/**
	 * 自定义标签的内部的模板
	 * @param source 模板资源
	 * @return String 处理后的模板
	 * @throws FileNotFoundException 
	 * @throws URISyntaxException 
	 * @throws MalformedURLException 
	 */
	@SuppressWarnings("rawtypes")
	protected String processSource(String source, SimpleHash hash, Map params) 
			throws TemplateModelException, FileNotFoundException, URISyntaxException, MalformedURLException {
		if (StringUtils.isEmpty(source)) {
			String path = null;
			
			// 获得路径
			path = getTemplatePath(params);//getString("path", params, PAGE_NAME);
			// 对路径进行先期判断
			if (StringUtils.isEmpty(path)) {
				// 返回空资源
				return "";
			}
			HttpRequestHashModel requestModel;
			requestModel = (HttpRequestHashModel) hash.get(FreemarkerManager.KEY_REQUEST);
			HttpServletRequest request = requestModel.getRequest();
			ServletContext context = request.getSession(true).getServletContext();
			// 使用web取得资源
			URL url = context.getResource(path);
			if (url == null) {
				// 使用线程Loader取得资源
				url = ClassLoaderUtils.getResource(path, this.getClass());
			}
			if (url != null) {
				StringBuffer sb = StringUtils.textReader(url.toURI());
				source = sb.toString();
			}
		} 
		return source;
	} 
	
	/**
	 * 后处理函数
	 * @param writer Writer
	 */
	public abstract void afterInvoke(Writer writer);
	
	public void setRender(boolean isRender) {
		this.isRender = isRender;
	}

	/**
	 * 创建嵌套模板(子模板)的数据源
	 * 使用整体模板的数据,传递到嵌套模板中
	 * @param wrapper ObjectWrapper
	 * @param model TemplateHashModel 整体模板的数据Model
	 * @return SimpleHash
	 * @throws TemplateModelException
	 */
	protected SimpleHash createSimpleHash(ObjectWrapper wrapper, TemplateHashModel model) throws TemplateModelException {
		
		
		HttpRequestHashModel requestModel = (HttpRequestHashModel) model.get(FreemarkerManager.KEY_REQUEST);
		
		HttpServletRequest request = requestModel.getRequest(); 
		BeanModel stackModel = (BeanModel) model.get("stack");
		OgnlValueStack valueStack = null;
		if (stackModel != null) {
			Object vs = stackModel.getWrappedObject();
			if (vs != null && vs instanceof OgnlValueStack) {
				valueStack = (OgnlValueStack) vs;
			}
		}
		// 构造上下文Hash
		ScopesHashModel hash = new ScopesHashModel(wrapper, null, request, valueStack);
//		HttpRequestHashModel requestModel = (HttpRequestHashModel) model.get(FreemarkerManager.KEY_REQUEST);		
		hash.putUnlistedModel(FreemarkerManager.KEY_REQUEST, requestModel);
		hash.putUnlistedModel(FreemarkerManager.KEY_SESSION,
				model.get(FreemarkerManager.KEY_SESSION));
		hash.putUnlistedModel(FreemarkerManager.KEY_APPLICATION,
				model.get(FreemarkerManager.KEY_APPLICATION));
		hash.putUnlistedModel(FreemarkerManager.KEY_JSP_TAGLIBS,
				model.get(FreemarkerManager.KEY_JSP_TAGLIBS));
		hash.putUnlistedModel(FreemarkerManager.KEY_REQUEST_PARAMETERS_STRUTS,
				model.get(FreemarkerManager.KEY_REQUEST_PARAMETERS_STRUTS));
		// 基础地址
		hash.put("base", request.getContextPath());
		// 获得扩展名
		Site site = MultisiteContextHolder.getSite();
		String suffix = ".html";
		if (site != null) {
			suffix = getSuffix(site, suffix);
		}
		// 设置扩展名
		hash.put("suffix", suffix);		
		return hash;
	}
	
	public Object getValue(String key) {
		//ConfigurationManager manager = ConfigurationManager.getInstance();
		//List<DirectiveGroupModel> modelList = manager.getDirectiveGroupModelList();
		return null;
	}
	
	// ~~ get params ------------------------------------------------------------------------
	@SuppressWarnings("rawtypes")
	protected Object getParams(String key, Map params) {
		Object obj = null;
		if (params.containsKey(key)) {
			obj = params.get(key);
		}
		return obj;
	}
	
	@SuppressWarnings("rawtypes")
	public String getString(String key, Map params) throws TemplateModelException{
		return getString(key, params, null);
	}
	
	@SuppressWarnings("rawtypes")
	public Long getLong(String key, Map params) throws TemplateModelException{
		return getLong(key, params, null);
	}
	
	@SuppressWarnings("rawtypes")
	public Integer getInteger(String key, Map params) throws TemplateModelException{
		return getInteger(key, params, null);
	}
	
	@SuppressWarnings("rawtypes")
	public Boolean getBoolean(String key, Map params) throws TemplateModelException{
		return getBoolean(key, params, null);
	}
	
	/**
	 * 带有默认值的String型数据
	 * @param key 参数key
	 * @param params 参数模型
	 * @param defaultValue 默认值
	 * @return String 结果
	 * @throws TemplateModelException
	 */
	@SuppressWarnings("rawtypes")
	public String getString(String key, Map params, String defaultValue) throws TemplateModelException{
		Object obj = getParams(key, params);
		String value = "";
		if (obj instanceof TemplateNumberModel) {
			value = ((TemplateNumberModel)obj).toString();
		}
		
		if (obj instanceof TemplateScalarModel) {
			value = ((TemplateScalarModel)obj).getAsString();
		}
		
		if (obj instanceof TemplateDateModel) {
			value = String.valueOf(((TemplateDateModel)obj).getAsDate().getTime());
		}
		
		if (StringUtils.isEmpty(value)) {
			value = defaultValue;
		}
		return value;
	}
	
	/**
	 * 带有默认值的Long型数据
	 * @param key 参数key
	 * @param params 参数模型
	 * @param defaultValue 默认值
	 * @return Long 结果
	 * @throws TemplateModelException
	 */
	@SuppressWarnings("rawtypes")
	public Long getLong(String key, Map params, Long defaultValue) throws TemplateModelException{
		String value = getString(key, params);
		if (!StringUtils.isEmpty(value)) {
			// 如果输入16进制,则进行转换
			NumberFormat numFormat = NumberFormat.getInstance();
			ParsePosition parsePos = new ParsePosition(0);
			numFormat.setGroupingUsed(true);
			Number number = numFormat.parse(value, parsePos);
			if (parsePos.getIndex() != value.length()) {
				try {
					number = Long.parseLong(value, 16);
				} catch (NumberFormatException e) {
				}
			}
			return number == null ? null : number.longValue();
		}
		return defaultValue;
	}
	
	/**
	 * 带有默认值的Boolean型数据
	 * @param key 参数key
	 * @param params 参数模型
	 * @param defaultValue 默认值
	 * @return Boolean 结果
	 * @throws TemplateModelException
	 */
	@SuppressWarnings("rawtypes")
	public Boolean getBoolean(String key, Map params, Boolean defaultValue) throws TemplateModelException{
		String value = getString(key, params);
		if (!StringUtils.isEmpty(value)) {
			return Boolean.valueOf(value);
		}
		return defaultValue;
	}
	
	/**
	 * 带有默认值的Integer型数据
	 * @param key 参数key
	 * @param params 参数模型
	 * @param defaultValue 默认值
	 * @return Integer 结果
	 * @throws TemplateModelException
	 */
	@SuppressWarnings("rawtypes")
	public Integer getInteger(String key, Map params, Integer defaultValue) throws TemplateModelException{
		String value = getString(key, params);
		if (!StringUtils.isEmpty(value)) {
			return Integer.parseInt(value);
		}
		return defaultValue;
	}

	@SuppressWarnings("rawtypes")
	@Override
	public void validate(Map params) throws ParameterValidateException {
		List<Attribute> attributeList = model.getAttributeList();
		if (attributeList != null) {
			List<String> errors = new ArrayList<String>();
			for (Attribute attribute : attributeList) {
				if (attribute.isRequired()) {
					String key = attribute.getName();
					Object v = params.get(key);
					if (v == null || StringUtils.isEmpty(v.toString())) {
						// 必要参数为空,抛出异常
						errors.add("指令[@" + name + "] 的参数'" + key + "'不能空");
					}
				}
			}
			if (errors.size() > 0) {
				throw new ParameterValidateException(errors);
			}
		}
	}
	
	/**
	 * 获得url后缀
	 * @param site 站点模型
	 * @param defaultValue 默认值
	 * @return String
	 */
	private String getSuffix(Site site, String defaultValue) {
    	String suffix = site.getSuffix();
    	if (StringUtils.isNotEmpty(suffix)) {
    		if (suffix.charAt(0) != '.') {
    			suffix = '.' + suffix;
    		}
    	} else {
    		// 使用默认后缀
    		suffix = defaultValue;
    	}
    	return suffix;
	}
	
	/**
	 * 取得配置的默认值
	 * <br>
	 * 默认值参照自定义指令配置文件direvtive-config.xml
	 * @param key 指令参数key
	 * @return 指令参数的默认值
	 */
	protected String getDefaultValue(String key) {
		Attribute attribute = model.getAttributeMap().get(key);
		String defaultValue = null;
		if (attribute != null) {
			defaultValue = attribute.getDefaultValue();
		}
		return StringUtils.isEmpty(defaultValue) ? "" : defaultValue;
	}
	
	@Override
	public void setName(String name) {
		this.name = name;
	}

	@Override
	public void setDirectiveModel(DirectiveModel model) {
		this.model = model;
	}

	public DirectiveModel getDirectiveModel() {
		return model;
	}
}